import { Blackboard } from './Blackboard';
import { BaseNode, CategoryType, ActionStatus } from "./BaseNode";
import { createUUID } from "../utils/Utils";

import * as Decorators from "../decorators"
import * as Composites from "../composites"
import * as Actions from "../actions"
import { Tick } from './Tick';


export class BehaviorTree {
    id: string;
    title: string;
    description: string;
    properties: any;
    root: BaseNode;
    debug: any;

    constructor() {
        this.id          = createUUID();
        this.title       = 'The behavior tree';
        this.description = 'Default description';
        this.properties  = {};
        this.root        = null;
        this.debug       = null;
    }

    load(data: any, names?: any) {
        names = names || {};
        this.title       = data.title || this.title;
        this.description = data.description || this.description;
        this.properties  = data.properties || this.properties;
        let nodes = {};
        let id, spec, node;
        for (id in data.nodes) {
            spec = data.nodes[id];
            let Cls;
            if (spec.name in names) {
                Cls = names[spec.name];
            } else if (spec.name in Decorators) {
                Cls = Decorators[spec.name];
            } else if (spec.name in Composites) {
                Cls = Composites[spec.name];
            } else if (spec.name in Actions) {
                Cls = Actions[spec.name];
            } else {
                // Invalid node name
                throw new EvalError('当前执行的节点不存在');
            }
            console.log('spec.name', spec.name)
            node = new Cls(spec.properties);
            node.id = spec.id || node.id;
            node.title = spec.title || node.title;
            node.description = spec.description || node.description;
            node.properties = spec.properties || node.properties;
            nodes[id] = node;
        }
        
        for (id in data.nodes) {
            spec = data.nodes[id];
            node = nodes[id];
            if (node.category === CategoryType.COMPOSITE && spec.children) {
                for (var i=0; i<spec.children.length; i++) {
                    var cid = spec.children[i];
                    node.children.push(nodes[cid]);
                }
            } else if (node.category === CategoryType.DECORATOR && spec.child) {
                node.child = nodes[spec.child];
            }
        }
        this.root = nodes[data.root];
    }


    tick(target: any, blackboard: Blackboard) : ActionStatus {
        const tick = new Tick();
        tick.debug      = this.debug;
        tick.target     = target;
        tick.blackboard = blackboard;
        tick.tree       = this;
        let status = this.root._execute(tick);
        let lastOpenNodes = blackboard.get('openNodes', this.id);
        let currOpenNodes = tick._openNodes.slice(0)
        let start = 0;
        let i = 0;
        for (i = 0; i < Math.min(lastOpenNodes.length, currOpenNodes.length); i++) {
            start = i + 1;
            if (lastOpenNodes[i] !== currOpenNodes[i]) {
                break;
            }
        }
        for (i = lastOpenNodes.length-1; i >= start; i--) {
            lastOpenNodes[i]._close(tick);
        }
        blackboard.set('openNodes', currOpenNodes, this.id);
        blackboard.set('nodeCount', tick._nodeCount, this.id);
        return status;
    }

    dump() {
        let data: any = {};
        let customNames = [];
        data.title        = this.title;
        data.description  = this.description;
        data.root         = (this.root)? this.root.id:null;
        data.properties   = this.properties;
        data.nodes        = {};
        data.custom_nodes = [];
        if (!this.root) {
            return data;
        } 
        let stack = [this.root];
        while (stack.length > 0) {
            let node = stack.pop();
            let spec: any = {};
            spec.id = node.id;
            spec.name = node.name;
            spec.title = node.title;
            spec.description = node.description;
            spec.properties = node.properties;
            spec.parameters = node.parameters;
        
            // verify custom node
            let proto = (node.constructor && node.constructor.prototype);
            let nodeName = (proto && proto.name) || node.name;
            if (!Decorators[nodeName] && !Composites[nodeName] && !Actions[nodeName] && customNames.indexOf(nodeName) < 0) {
                let subdata: any = {};
                subdata.name = nodeName;
                subdata.title = (proto && proto.title) || node.title;
                subdata.category = node.category;
        
                customNames.push(nodeName);
                data.custom_nodes.push(subdata);
            }
        
            // store children/child
            if (node.category === CategoryType.COMPOSITE && node.children) {
                let children = [];
                for (var i=node.children.length-1; i>=0; i--) {
                children.push(node.children[i].id);
                stack.push(node.children[i]);
                }
                spec.children = children;
            } else if (node.category === CategoryType.DECORATOR && node.child) {
                stack.push(node.child);
                spec.child = node.child.id;
            }
        
            data.nodes[node.id] = spec;
        }
    
        return data;        
    }
}